﻿namespace Busconeitor.BusinessLayer.Util
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Globalization;
    using System.Linq;
    using System.Text.RegularExpressions;

    public class BusconeitorCore
    {
        public BusconeitorCore()
        {
            var docs = new List<string>
                {
                    "gold silver truck", // Query
                    "shipment of gold damaged in a fire", // Doc1
                    "delivery of silver arrived in a silver truck", // Doc2
                    "shipment of gold arrived in a truck", // Doc3
                    "a truck of gold with silver surfer" 
                };

            this.SearchWithVectorSpaceModel(docs);
        }

        private void SearchWithVectorSpaceModel(List<string> docs)
        {
            var documentTermVector = new Hashtable(); // Hashtable to hold Document Term Vector
            var wordlist = new List<string>(); // List of terms found in documents
            var sortedList = new Dictionary<double, string>(); // Documents ranked by VSM with angle value

            this.CreateWordList(wordlist, docs);
            this.CreateVector(documentTermVector, wordlist, docs);
            this.Classify(documentTermVector, wordlist, sortedList);

            var dictionary = sortedList;

            foreach (var item in dictionary.Reverse())
            {
                Console.WriteLine("{0} -> Doc{1}", item.Key, item.Value);
            }
        }

        private void CreateWordList(List<string> wordlist, List<string> docs)
        {
            foreach (var doc in docs)
            {
                wordlist = this.GetWordList(wordlist, doc);
            }
        }

        private List<string> GetWordList(List<string> wordlist, string query)
        {
            var exp = new Regex("\\w+", RegexOptions.IgnoreCase);
            var matchCollection = exp.Matches(query);

            foreach (Match match in matchCollection)
            {
                if (!wordlist.Contains(match.Value))
                {
                    wordlist.Add(match.Value);
                }
            }

            return wordlist;
        }

        private void CreateVector(Hashtable documentTermVector, List<string> wordlist, List<string> docs)
        {
            for (var j = 0; j < docs.Count; j++)
            {
                var queryVector = new double[wordlist.Count];

                for (var i = 0; i < wordlist.Count; i++)
                {
                    var termFrecuencyIdFrecuency = this.GetTermFrecuency(docs[j], wordlist[i]) * this.GetIdFrecuency(wordlist[i], docs);
                    queryVector[i] = termFrecuencyIdFrecuency;
                }

                if (j == 0)
                {
                    // is it a query?
                    documentTermVector.Add("Query", queryVector);
                }
                else
                {
                    documentTermVector.Add(j.ToString(CultureInfo.InvariantCulture), queryVector);
                }
            }
        }

        private void Classify(
            Hashtable documentTermVector, List<string> wordlist, Dictionary<double, string> sortedList)
        {
            var enumerator = documentTermVector.GetEnumerator();
            var queryVector = new double[wordlist.Count];

            Array.Copy((double[])documentTermVector["Query"], queryVector, wordlist.Count);

            while (enumerator.MoveNext())
            {
                if (enumerator.Key.ToString() != "Query")
                {
                    var temp = this.CosineTheta(queryVector, (double[])enumerator.Value);
                    sortedList.Add(temp, enumerator.Key.ToString());
                }
            }

            // foreach (string item in documentTermVector.Keys)
            // {
            //     if (item != "Query")
            //     {
            //         var temp = CosineTheta(queryVector, (double[])enumerator.Value);
            //         sortedList.Add(temp, item);
            //     }
            // }
        }

        private double DotProduct(double[] v1, double[] v2)
        {
            double product = 0.0;

            if (v1.Length == v2.Length)
            {
                for (int i = 0; i < v1.Length; i++)
                {
                    product += v1[i] * v2[i];
                }
            }

            return product;
        }

        private double VectorLength(double[] vector)
        {
            double length = 0.0;

            for (int i = 0; i < vector.Length; i++)
            {
                length += Math.Pow(vector[i], 2);
            }

            return Math.Sqrt(length);
        }

        private double GetTermFrecuency(string document, string term)
        {
            var queryTerms = Regex.Split(document, "\\s");
            double count = 0;

            foreach (var t in queryTerms)
            {
                if (t == term)
                {
                    count++;
                }
            }

            return count;
        }

        private double GetIdFrecuency(string term, List<string> docs)
        {
            double documentFrecuency = 0.0;

            // get term frequency of all of the sentences except for the query
            for (var i = 1; i < docs.Count; i++)
            {
                if (docs[i].Contains(term))
                {
                    documentFrecuency++;
                }
            }

            // get sentence count
            double document = docs.Count - 1; // excluding the query
            double idFrecuency = 0.0;

            if (documentFrecuency > 0)
            {
                idFrecuency = Math.Log(document / documentFrecuency);
            }

            return idFrecuency;
        }

        private double CosineTheta(double[] vector1, double[] vector2)
        {
            var lengthVector1 = this.VectorLength(vector1);
            var lengthVector2 = this.VectorLength(vector2);
            var dotProduct = this.DotProduct(vector1, vector2);

            return dotProduct / (lengthVector1 * lengthVector2);
        }
    }
}